/*
 * NodeAdder adds a new node to the system. We ideally should not have to
 * provide any command line arguments - but that can be added as an extra
 * feature basic implementation requires the user to provide a node_id and a
 * ring size (i.e. and m value)
 * 
 * 
 * TCSS 558 Team 6 Spring 2013
 */

package node;

import java.io.IOException;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

import message.Message;

/**
 * NodeAdder adds a new node to the system. We ideally should not have to
 * provide any command line arguments - but that can be added as an extra
 * feature basic implementation requires the user to provide a node_id and a
 * ring size (i.e. and m value)
 * 
 * 
 * @author TCSS 558 Team 6
 * @version Spring 2013
 */
public final class NodeAdder {

  /**
   * The node that is added to the system by this adder. Has to be a static type
   * to avoid garbage collection.
   */
  private static Node my_node;

  private NodeAdder() {
    // do nothing
  }

  /**
   * The main method for NodeAdder. Initializes an actual node with a given
   * Node_ID and an M value and binds it to the registry.
   * 
   * TODO 2 Later, we can implement a way of making the node_id and m_value
   * optional args - beyond scope of current assignment
   * 
   * @param the_args An array of Strings containing the command line arguments.
   *          The first argument is the host name, the second is the port
   *          number, the third is the registry name, the fourth is the node id
   *          and the fifth is m value.
   */
  public static void main(final String... the_args) {
    // get a reference to the remote registry.
    final String host = the_args[0];
    final int port = Integer.parseInt(the_args[1]);
    final String registry_name = the_args[2];
    // ideally there should be no need to input node id
    // the system should assign node id with some logic that
    // ensures an even distribution of actual nodes around the ring.
    // implementation beyond current scope of this project
    try {
      final Registry bootstrap = LocateRegistry.getRegistry(host, port);
      final Registry registry = (Registry) bootstrap.lookup(registry_name);
      String[] registered_nodes = registry.list();

      // validates arguments provided
      final int m_value = Validator.ValidateM(registry, the_args[4]);
      final int node_id = Validator.validateNode(registered_nodes, the_args[3], m_value);
      // binds the new node
      my_node = new ActualNode(node_id, m_value, registry);
      System.out.println("Adding new actual node: node id: " + node_id + " to the system");
      System.out.println("Binding node " + node_id + " to registry now.");
      registry.bind("node_id_" + node_id, UnicastRemoteObject.exportObject(my_node, 0));
      System.out.println("Node " + node_id + " bound to registry.");

      if (registered_nodes.length != 0) {
        // prints the list of registered nodes.
        System.out.println("List of registered nodes:");
        for (int i = 0; i < registered_nodes.length; i++) {
          System.out.println("Node registered: " + registered_nodes[i]);
        }

        // ask the existing nodes to give the data for the virtual nodes
        // set up the virtual nodes and build up all the channels.

        System.out.println("Node " + node_id +
                           " Initializing the new node channels.....................");
        System.out.println("Node " + node_id +
                           " Initializing the new (empty) virtual nodes.............");
        System.out.println("Node " + node_id +
                           " Updating Successor & Predecessor actual nodes..........");
        ((Node) my_node).initialize();
        ((Node) my_node).updateActualNodes();
        System.out.println("Node " + node_id +
                           " Updating channels of neighbors pointing to the new node");
        boolean success = ((Node) my_node).updateNetworkChannels();
        if (success) {
          System.out.println("Node " + node_id +
                             " Initializing data transfer in to virtual nodes.......");
          ((Node) my_node).initializeDataTransfer_Add();
        }
      } else { // no nodes exist in the system
        ((Node) my_node).initialize();
        System.out.println("System initialized.");
      }
      
    } catch (final RemoteException e) {
      System.err.println("a problem occurred at remote method call." + e);
      System.exit(1);
    } catch (final NotBoundException e) {
      System.err.println("The object doesn't bind.");
      System.exit(1);
    } catch (final IOException e) {
      e.printStackTrace();
    } catch (final AlreadyBoundException e) {
      System.err.println("Node has already bound.");
      System.exit(1);
    } catch (final ArrayIndexOutOfBoundsException e) {
      System.err.println("Usage:java NodeAdder <host> "
                         + "<port> <registry name> <node_id> <m value>");
      e.printStackTrace();
    }
  }
}

// Use later to make the node leave the network.
// registry.unbind("node_id_" + node_id);
// UnicastRemoteObject.unexportObject(my_node, true);

